package com.mini;

import javax.sql.DataSource;
import java.sql.*;

public class MiniQueryRunner extends MiniAbstractQueryRunner {

    /* 一般工具类都会声明一个无参构造；因为子类在初始化的时候，必定会调用父类的构造
     * 函数，默认是无参构造；如果父类没有无参构造，默认的调用方式将会行不通，只能手
     * 动指定调用的有参构造 */
    public MiniQueryRunner () {
        super();
    }

    public MiniQueryRunner(boolean pmdKnownBroken) {
        super(pmdKnownBroken);
    }
    public MiniQueryRunner(DataSource ds) {super(ds);}
    public MiniQueryRunner(boolean pmdKnownBroken, DataSource ds) {super(pmdKnownBroken,ds);}

    /* 执行修改语句(将最完整的执行语句私有化，通过其他重载语句调用执行；因为closeConn是用来标记是否需要关闭数据库连接，这个值不需要被用户传递)
     * conn 表示一个数据库连接
     * 说明：一个连接对象数据库可以提供信息描述它的表、它使用的语法和数据的储存过程，这些信息可以通过获取元数据获得
     * closeConn 一个开关，指定数据库连接是否需要被关闭。使用者自己传递的连接需要自己关闭，如果是工具内部创建的连接将自动关闭
     * sql 将要被预编译的SQL语句（可以带占位符"?"的那种）
     * params 可变长度的参数（实际上就是一个数组） */
    private int update(Connection conn, boolean closeConn, String sql, Object... params) throws SQLException {
        // 为了代码健壮，先对参数进行一系列的检查，守护代码
        if (conn == null) {
            throw new SQLException("Null connection 空的数据库连接对象");
        }
        // 如果sql语句为空并且连接必须关闭，则关闭连接对象
        if (sql == null) {
            if (closeConn) {
                close(conn);
            }
            throw new SQLException("Null SQL statement 空的SQL预处理对象");
        }

        /* 预编译SQL语句对象，SQL语句被预编译并储存在该对象中
         * 该对象中储存的SQL语句可以带参数的也可以不带参数；利用该对象可以有效地多次执行一个SQL语句 */
        PreparedStatement stmt = null;
        int rows = 0;   // 执行返回的结果

        try {
            // 创建预编SQL对象，用于向数据库发送参数化的SQL
            stmt = this.prepareStatement(conn, sql);
            // 将参数填充到预编SQL对象中
            this.fillStatement(stmt, params);
            // 根据完整的预编SQL对象执行SQL语句
            rows = stmt.executeUpdate();
        } catch (SQLException e) {
            // 将产生的异常包装成一个包含更多信息的异常对象抛出
            this.rethrow(e, sql, params);
        } finally {
            close(stmt);
            if (closeConn) {
                close(conn);
            }
        }

        return rows;
    }
    public int update(String sql, Object... params) throws SQLException {
        Connection conn = this.prepareConnection();
        return this.update(conn, true, sql, params);
    }
    public int update(String sql, Object param) throws SQLException {
        Connection conn = this.prepareConnection();
        return this.update(conn, true, sql, new Object[]{param});
    }
    // 遵循写类库“把麻烦留给自己，把方便留给别人”的原则，解决sql语句中没有问号（无参数语句 如：delete from xx where id = 5）
    public int update(String sql) throws SQLException {
        Connection conn = this.prepareConnection();
        return this.update(conn, true, sql, (Object[]) null);
    }
    public int update(Connection conn, String sql, Object... params) throws SQLException {
        return update(conn, false, sql, params);
    }
    public int update(Connection conn, String sql, Object param) throws SQLException {
        return this.update(conn, false, sql, new Object[]{param});
    }
    public int update(Connection conn, String sql) throws SQLException {
        return this.update(conn, false, sql, (Object[]) null);
    }

    /* 执行查询语句（依照修改语句，将最完整的执行语句私有化）
     * rsh 统一的结果集处理对象，需要传递一个它的实现类对象；可以实现将查询的结果转换成其他类型的数据对象返回 */
    private <T> T query (Connection conn, boolean closeConn, String sql, MiniResultSetHandler<T> rsh, Object... params) throws SQLException {
        // 防御性代码，保持代码的健壮性，如果连接为空就直接抛出异常
        if (conn == null ) {
            throw new SQLException("Null connection 空的数据库连接对象");
        }
        // 如果SQL语句或者结果集为空都将抛出异常
        if (sql == null) {
            if (closeConn) {
                close(conn);
            }
            throw new SQLException("Null SQL statement 没有可执行的SQL语句");
        }
        if (rsh == null) {
            if (closeConn) {
                close(conn);
            }
            throw new SQLException("Null ResultSetHandler 没有可以封装的结果集对象");
        }

        PreparedStatement stmt = null;
        /* 结果集对象 表示数据库结果集的数据表；内部维护着一个指向当前数据行的游标
         * 提供的get方法可以读取当前数据行的某一列的值；可以根据列的索引号或者列的名称去获取 */
        ResultSet rs = null;
        T result = null;

        try {
            stmt = this.prepareStatement(conn, sql);
            this.fillStatement(stmt, params);
            rs = stmt.executeQuery();
            // 封装结果集
            result = rsh.handle(rs);

        } catch (SQLException e) {
            this.rethrow(e, sql, params);
        } finally {
            try {
                close(rs);
            } finally {
                close(stmt);
                if (closeConn) {
                    close(conn);
                }
            }
        }

        return result;
    }
    public <T> T query (String sql, MiniResultSetHandler<T> rsh, Object... params) throws SQLException {
        Connection conn = this.prepareConnection();
        return this.query(conn, true, sql, rsh, params);
    }
    public <T> T query (String sql, MiniResultSetHandler<T> rsh) throws SQLException {
        Connection conn = this.prepareConnection();
        // 这里将null强转为Object[]。如果不强转，反射获取到null的类型就是Object；强转后，获取到null的类型就是Object[]
        return this.query(conn, true, sql, rsh, (Object[]) null);
    }
    public <T> T query (Connection conn, String sql, MiniResultSetHandler<T> rsh, Object... params) throws SQLException {
        return this.query(conn,false,sql,rsh,params);
    }
    public <T> T query(Connection conn, String sql, MiniResultSetHandler<T> rsh) throws SQLException {
        return this.query(conn,false,sql,rsh,(Object[]) null);
    }

    /* 执行给定的插入语句，结果返回一个数据库中生成的主键ID */
    private <T> T insert (Connection conn, boolean closeConn, String sql, MiniResultSetHandler<T> rsh, Object... params) throws SQLException {
        // 防御性代码
        if (conn == null) {
            throw new SQLException("\"Null connection 空的数据库连接对象\"");
        }
        // 如果SQL语句或者结果集为空，都将直接抛出异常
        if (sql == null) {
            if (closeConn) {
                throw new SQLException("Null SQL statement 没有可执行的SQL语句");
            }
        }
        if (rsh == null) {
            if (closeConn) {
                throw new SQLException("Null ResultSetHandler 没有可以封装的结果集对象");
            }
        }

        PreparedStatement stmt = null;
        T generatedKey = null;

        try {
            // 生成一个可以自动检索自增key的预处理SQL对象；传递的常量表示是否需要获取自增长key
            PreparedStatement preparedStatement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            // 将参数填充到预处理对象中
            this.fillStatement(preparedStatement,params);
            preparedStatement.executeUpdate();
            // 检索由于执行此预编译SQL对象而自动生成的任何KEY；
            ResultSet resultSet = preparedStatement.getGeneratedKeys();
            generatedKey = rsh.handle(resultSet);
        } catch (SQLException e) {
            this.rethrow(e,sql,params);
        } finally {
            close(stmt);
            if (closeConn) {
                close(conn);
            }
        }
        return generatedKey;
    }
    public <T> T insert (Connection conn, String sql, MiniResultSetHandler<T> rsh, Object... params) throws SQLException {
        return this.insert(conn,false,sql,rsh,params);
    }
    public <T> T insert (Connection conn, String sql, MiniResultSetHandler<T> rsh) throws SQLException {
        return this.insert(conn,false,sql,rsh, (Object[]) null);
    }
    public <T> T insert (String sql, MiniResultSetHandler<T> rsh, Object... params) throws SQLException {
        Connection connection = this.prepareConnection();
        return this.insert(connection,true,sql,rsh,params);
    }
    public <T> T insert (String sql, MiniResultSetHandler<T> rsh) throws SQLException {
        Connection connection = this.prepareConnection();
        return this.insert(connection,true,sql,rsh, (Object[]) null);
    }

}
